package cn.ckx.coding.config;

import cn.ckx.coding.common.utils.hash.ByteUtils;
import cn.ckx.coding.common.utils.redis.RedisKeys;
import cn.ckx.coding.properties.NacosProperties;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

/**
 * 分布式id生成策略
 * k8s环境无实体网卡，使用redis获取workId和centerId
 * 
 * @author kunxin.cai
 */
@Slf4j
@SpringBootConfiguration
@EnableScheduling
public class DatabaseIdentifierConfig {

    /**
     * 默认数据中心的ID
     */
    private static final int DEFAULT_DATA_CENTER_ID = 0;
    /**
     * 最大节点数
     */
    private static final int MAX_WORKER_ID = 31;
    /**
     * 节点配置超时时间
     */
    private static final int TIMEOUT_IN_SECONDS = 3600;
    
    private String workerKey;

    private final ObjectMapper objectMapper;
    private final RedisTemplate<String, String> redisTemplate;
    private final NacosProperties properties;

    public DatabaseIdentifierConfig(
            ObjectMapper objectMapper,
            RedisTemplate<String, String> redisTemplate,
            NacosProperties properties
    ) {
        this.objectMapper = objectMapper;
        this.redisTemplate = redisTemplate;
        this.properties = properties;
    }

    @Bean
    public IdentifierGenerator identifierGenerator() {
        // 目前仅考虑单个数据中心的部署
        int dataCenterId = DEFAULT_DATA_CENTER_ID;
        int workerId = -1;
        String nodeInfo = getNodeInfo();
        // 默认是spring.application.name的值
        String serviceName = properties.getService().getName();
        for (int i = 0; i < MAX_WORKER_ID; i++) {
            /* 
            IDG : 主键生成器
            DC : 数据中心
            格式: IDG_DC数据中心_服务名称_节点ID
            */
            String key = RedisKeys.getCodingRedisKey("identifierGenerator",
                    "IDG_DC" + dataCenterId + "_" + serviceName + "_" + i,"string");
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(key, nodeInfo, TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
            if (locked != null && locked) {
                workerId = i;
                workerKey = key;
                break;
            }
        }
        if (workerId == -1) {
            throw new ExceptionInInitializerError("无法初始化节点，当前可用的workerId");
        }
        return new DefaultIdentifierGenerator(dataCenterId, workerId);
    }

    @Scheduled(cron = "0 */50 * * * *")
    public void updateTTL() {
        Boolean state = redisTemplate.expire(workerKey, TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
        log.debug("修改workerId锁定资源的有效期，workerKey : {}，锁定结果 : {}", workerKey, state);
    }

    private String getNodeInfo() {
        try {
            ObjectNode objectNode = objectMapper.createObjectNode();
            // 所有网卡
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface network = networkInterfaces.nextElement();
                if (network.isLoopback()) {
                    break;
                }
                objectNode.put("name", network.getDisplayName());
                byte[] mac = network.getHardwareAddress();
                objectNode.put("macAddress", ByteUtils.macToString(mac));
                ArrayNode ipAddresses = objectMapper.createArrayNode();
                objectNode.set("ipAddresses", ipAddresses);

                Enumeration<InetAddress> inetAddresses = network.getInetAddresses();
                while (inetAddresses.hasMoreElements()) {
                    InetAddress ip = inetAddresses.nextElement();
                    ipAddresses.add(ip.getHostAddress());
                }
            }
            return objectMapper.writeValueAsString(objectNode);
        } catch (Exception e) {
            log.error("无法获取IP地址：" + e.getMessage(), e);
        }
        return "{name: '未知主机'}";
    }
}
